﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EpidemicSimulator.stopconditions;
using EpidemicSimulator.states;
using EpidemicSimulator.strategies;
using QuickGraph;
using QuickGraph.Serialization;
using GraphInformation;
using System.Xml;
using EpidemicSimulator.reports;
using System.Runtime.Remoting.Messaging;
using EdgeWithAttributes;
namespace EpidemicSimulator
{
    [Serializable]
    public class Simulation
    {
        public List<StopCondition> stopConditions { get; set; }
        public bool simulationStarted;
        public bool simulationRunning;
        public long simTime;
        public AdjacencyGraph<DiseaseModelNode, DiseaseModelEdge> diseaseModelGraph { get; set; }
        public UndirectedGraph<SocialNetworkNode, EdgeWithAttributes<SocialNetworkNode>> socialNetwork { get; set; }
        private SimulationReport simReport;
        private List<StateChangePlan> stateChangePlans;

        private SimulationStopCallback callback = null;

        public delegate void SimulationStopCallback(SimulationReport report);

        private int maxContactsPerStep = 1;
        private int minContactsPerStep = 1;

        public Simulation(AdjacencyGraph<DiseaseModelNode, DiseaseModelEdge> diseaseModelGraph, UndirectedGraph<SocialNetworkNode, EdgeWithAttributes<SocialNetworkNode>> socialNetwork)
        {
            this.diseaseModelGraph = diseaseModelGraph;
            this.socialNetwork = socialNetwork;
            stopConditions = new List<StopCondition>();
        }

        public void addStopCondition(StopCondition stopCondition)
        {
            stopConditions.Add(stopCondition);
        }

        public void addStateChangePlan(StateChangePlan stateChangePlan)
        {
            if (stateChangePlan.applyTime == simTime)
            {
                stateChangePlan.applyStrategy(socialNetwork, diseaseModelGraph);
            }
            stateChangePlans.Add(stateChangePlan);
        }

        public void setMaxContactsPerStep(int maxContacts)
        {
            maxContactsPerStep = maxContacts;
        }
        public void setMinContactsPerStep(int minContacts)
        {
            minContactsPerStep = minContacts;
        }
        public void reset()
        {
            simulationStarted = false;
            simulationRunning = false;
            simTime = 0;

            DiseaseModelNode defaultState = null;
            foreach (DiseaseModelNode diseaseNode in diseaseModelGraph.Vertices)
            {
                if (diseaseNode.defaultState)
                {
                    defaultState = diseaseNode;
                    break;
                }
            }

            foreach (SocialNetworkNode socialNode in socialNetwork.Vertices)
            {
                socialNode.socialNetwork = socialNetwork;
                if (!socialNode.Keys.ContainsKey("stateName"))
                {
                    socialNode.state = State.getStateInstance(defaultState.stateClass);
                    socialNode.state.stateNode = defaultState;
                    socialNode.state.diseaseModelGraph = diseaseModelGraph;
                    socialNode.state.node = socialNode;
                    socialNode.state.maxContacsPerStep = maxContactsPerStep;
                    socialNode.state.minContacsPerStep = minContactsPerStep;
                    socialNode.newState = socialNode.state;
                }
                else
                {
                    foreach (DiseaseModelNode diseaseNode in diseaseModelGraph.Vertices)
                    {
                        if (diseaseNode.stateName == socialNode.Keys["stateName"])
                        {
                            socialNode.state = State.getStateInstance(diseaseNode.stateClass);
                            socialNode.state.stateNode = diseaseNode;
                            socialNode.state.diseaseModelGraph = diseaseModelGraph;
                            socialNode.state.node = socialNode;
                            socialNode.state.maxContacsPerStep = maxContactsPerStep;
                            socialNode.state.minContacsPerStep = minContactsPerStep;
                            socialNode.newState = socialNode.state;
                        }
                    }
                }
            }

            simReport = new SimulationReport();
            simReport.dataSets = new SimulationReportDataSet[diseaseModelGraph.VertexCount];
            var diseaseStatesArray = diseaseModelGraph.Vertices.ToArray();
            for (int i = 0; i < diseaseModelGraph.VertexCount; i++)
            {
                simReport.dataSets[i] = new SimulationReportDataSet();
                simReport.dataSets[i].label = diseaseStatesArray[i].stateName;
                simReport.dataSets[i].color = diseaseStatesArray[i].nodeColor;
            }

            stateChangePlans = new List<StateChangePlan>();
        }

        public static void runSimulation(Object o)
        {
            Simulation simulation = (Simulation)o;
            if (!simulation.simulationStarted)
            {
                simulation.simulationStarted = true;
                simulation.simulationRunning = true;
            }

            while (simulation.simulationRunning)
            {
                simulation.nextSimStep();
            }
        }

        public void setSimulationStopCallback(SimulationStopCallback callback)
        {
            this.callback = callback;
        }

        public void nextSimStep()
        {
            if (!simulationStarted)
            {
                simulationStarted = true;
                simulationRunning = true;
            }

            if (simulationRunning)
            {
                simTime++;
                foreach (var plan in stateChangePlans)
                {
                    if (plan.applyTime == simTime)
                    {
                        plan.applyStrategy(socialNetwork, diseaseModelGraph);
                    }
                }

                foreach (SocialNetworkNode node in socialNetwork.Vertices)
                {
                    node.stateAction(simTime);
                }

                foreach (SocialNetworkNode node in socialNetwork.Vertices)
                {
                    node.state.update(simTime);
                }

                foreach (SimulationReportDataSet dataSet in simReport.dataSets)
                {
                    int counter = 0;
                    foreach (SocialNetworkNode node in socialNetwork.Vertices)
                    {
                        if (node.state.stateNode.stateName == dataSet.label)
                        {
                            counter++;
                        }
                    }
                    dataSet.addValue(counter);
                }

                //check if any stop condition occured
                foreach (StopCondition cond in stopConditions)
                {
                    if (cond.check(this))
                    {
                        simulationRunning = false;

                        if (callback != null)
                        {
                            callback(simReport);
                        }
                        return;
                    }
                }
                //simTime++;
            }
        }

        public UndirectedGraph<SocialNetworkNode, EdgeWithAttributes<SocialNetworkNode>> getSimulationResult()
        {
            if (simulationStarted) return socialNetwork;
            return null;
        }

        public Graph getSimulationResultForSilverlight()
        {
            Graph result = new Graph();
            result.ID = Guid.NewGuid();
            result.Vertices = new List<GraphElement>();
            result.Edges = new List<GraphEdge>();
            Dictionary<SocialNetworkNode, GraphElement> map = new Dictionary<SocialNetworkNode, GraphElement>();
            foreach (EdgeWithAttributes<SocialNetworkNode> edge in socialNetwork.Edges)
            {

                GraphElement source = null;
                if (map.ContainsKey(edge.Source))
                {
                    source = map[edge.Source];
                }

                GraphElement target = null;
                if (map.ContainsKey(edge.Target))
                {
                    target = map[edge.Target];
                }

                if (source == null)
                {
                    var node = edge.Source;
                    source = GetNode(map, node);
                    source.GraphID = result.ID;
                    result.Vertices.Add(source);
                }

                if (target == null)
                {
                    var node = edge.Target;
                    target = GetNode(map, node);
                    target.GraphID = result.ID;
                    result.Vertices.Add(target);
                }

                result.Edges.Add(new GraphEdge(){ ID = Guid.NewGuid(), GraphID = result.ID, Source = source.ID, Target = target.ID});
            }
            return result;
        }

        private GraphElement GetNode(Dictionary<SocialNetworkNode, GraphElement> map, SocialNetworkNode node)
        {
            GraphElement source;
            source = new GraphElement();
            map.Add(node, source);
            source.x = node.x;
            source.Keys = new List<PairKeyValue>();
            source.y = node.y;         
            source.ID = Guid.NewGuid();
            source.Height = node.Height;
            source.Width = node.Width;
            //source.Id = source.GetHashCode();
            for (int i = 0; i < node.Keys.Count; i++)
            {

                var k = node.Keys.ElementAt(i);
                PairKeyValue v = new PairKeyValue();
                if (k.Key != "stateName")
                {
                    v.Key = k.Key;
                    v.Value = k.Value;
                            
                    source.Keys.Add(v);
                }
                else
                {
                    v.Key = k.Key;

                    if (node.state != null)
                    {
                        v.Value = node.state.stateNode.stateName;
                    }
                    else
                        v.Value = "Susceptible";
                            
                    source.Keys.Add(v);
                }

            }
            return source;
        }

        public void saveSimulationResultToGraphML(string graphMLFile)
        {
            if (simulationStarted)
            {
                var vertexIdentity = new VertexIdentity<SocialNetworkNode>(SocialNetworkNode.vertexIdentity);
                var edgeIdentity = new EdgeIdentity<SocialNetworkNode, EdgeWithAttributes<SocialNetworkNode>>(SocialNetworkNode.edgeIdentity<SocialNetworkNode, EdgeWithAttributes<SocialNetworkNode>>);

                using (var writer = new XmlTextWriter(graphMLFile, Encoding.UTF8))
                {
                    writer.WriteStartDocument();
                    socialNetwork.SerializeToGraphML(writer, vertexIdentity, edgeIdentity);
                }
            }
        }

        public SimulationReport getSimulationReport()
        {
            return simReport;
        }

        public void removeNode(Guid id)
        {
            foreach (SocialNetworkNode node in socialNetwork.Vertices)
            {
                if (node.Id == id)
                {
                    socialNetwork.RemoveVertex(node);
                    return;
                }
            }
        }

        public void removeEdge(Guid sourceId, Guid targetId)
        {
            foreach (EdgeWithAttributes<SocialNetworkNode> edge in socialNetwork.Edges)
            {
                if ((edge.Source.Id == sourceId && edge.Target.Id == targetId) || (edge.Source.Id == targetId && edge.Target.Id == sourceId))
                {
                    socialNetwork.RemoveEdge(edge);
                    return;
                }
            }
        }

        public void changeNodeState(Guid nodeId, string newStateName)
        {
            foreach (SocialNetworkNode node in socialNetwork.Vertices)
            {
                if (node.Id == nodeId)
                {
                    node.state.changeState(newStateName);
                    return;
                }
            }
        }

        public void makeStateChanges(List<StateChangePlan> stateChangePlans)
        {
            if (stateChangePlans != null)
            {
                foreach (StateChangePlan plan in stateChangePlans)
                {
                    plan.applyStrategy(socialNetwork, diseaseModelGraph);
                }
            }
        }
    }
}
